﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using System.Xml;
using Notoric.Model.Internal;
using Notoric.Model.Packaging;
using Notoric.Model.Validation;

namespace Notoric.Model.Elements
{
    /// <summary>
    /// Base class for elements of the Notoric project (model).
    /// </summary>
    /// <remarks>
    /// <para>
    /// The <see cref="ModelElement"/> implements the <see cref="IDisposable"/> interface.
    /// When an element is removed from the package, its <see cref="Dispose"/> method should 
    /// be called. The element will reclaim its ID from the <see cref="Package.IDMap"/>
    /// and raise the <see cref="ElementDisposed"/> event that informs other lements
    /// which directly reference the disposed element that it has gone out of scope and they
    /// should remove their references.
    /// </para>
    /// </remarks>
    public abstract class ModelElement : INotifyPropertyChanged, IValidator, IDisposable
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="ModelElement"/> instance and sets its data to defaults.
        /// </summary>
        /// <remarks>
        /// <para>
        /// The constructor automatically attaches the <see cref="Notoric.Model.Packaging.PackagePart.Content_PropertyChanged"/>
        /// event listener to the <see cref="PropertyChanged"/> event of this element.
        /// </para>
        /// </remarks>
        /// <param name="part">
        /// <para>
        /// Type: <see cref="XmlPackagePart"/>
        /// </para>
        /// <para>
        /// The package part that owns this element.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="part"/> is <see langword="null"/>.
        /// </exception>
        protected ModelElement(XmlPackagePart part)
            : this(part, null as ModelElement)
        {
        }

        /// <summary>
        /// Creates a new <see cref="ModelElement"/> instance and loads its data from a given XML element.
        /// </summary>
        /// <remarks>
        /// <para>
        /// The constructor automatically attaches the <see cref="Notoric.Model.Packaging.PackagePart.Content_PropertyChanged"/>
        /// event listener to the <see cref="PropertyChanged"/> event of this element.
        /// </para>
        /// </remarks>
        /// <param name="part">
        /// <para>
        /// Type: <see cref="XmlPackagePart"/>
        /// </para>
        /// <para>
        /// The package part that owns this element.
        /// </para>
        /// </param>
        /// <param name="element">
        /// <para>
        /// Type: <see cref="XmlElement"/>
        /// </para>
        /// <para>
        /// The element that represents this object
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="part"/> or <paramref name="element"/> is <see langword="null"/>.
        /// </exception>
        protected ModelElement(XmlPackagePart part, XmlElement element)
            : this(part, null, element)
        {
        }

        /// <summary>
        /// Creates a new <see cref="ModelElement"/> instance and sets its data to defaults.
        /// </summary>
        /// <remarks>
        /// <para>
        /// The constructor automatically attaches the <see cref="Notoric.Model.Packaging.PackagePart.Content_PropertyChanged"/>
        /// event listener to the <see cref="PropertyChanged"/> event of this element.
        /// </para>
        /// </remarks>
        /// <param name="part">
        /// <para>
        /// Type: <see cref="XmlPackagePart"/>
        /// </para>
        /// <para>
        /// The package part that owns this element.
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="ModelElement"/>
        /// </para>
        /// <para>
        /// The model element that directly owns this element.<br/>
        /// This value is <see langword="null"/> if this element is root.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="part"/> is <see langword="null"/>.
        /// </exception>
        protected ModelElement(XmlPackagePart part, ModelElement owner)
        {
            if (part == null)
                throw new ArgumentNullException("part");

            if (owner == null)
                this.part = part;
            this.owner = owner;
            this.validationRules = new Dictionary<string, Rule[]>();

            this.PropertyChanged += new PropertyChangedEventHandler(part.Content_PropertyChanged);

            element = CreateObjectElement(part.Document);
        }

        /// <summary>
        /// Creates a new <see cref="ModelElement"/> instance and loads its data from a given XML element.
        /// </summary>
        /// <remarks>
        /// <para>
        /// The constructor automatically attaches the <see cref="Notoric.Model.Packaging.PackagePart.Content_PropertyChanged"/>
        /// event listener to the <see cref="PropertyChanged"/> event of this element.
        /// </para>
        /// </remarks>
        /// <param name="part">
        /// <para>
        /// Type: <see cref="XmlPackagePart"/>
        /// </para>
        /// <para>
        /// The package part that owns this element.
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="ModelElement"/>
        /// </para>
        /// <para>
        /// The model element that directly owns this element.<br/>
        /// This value is <see langword="null"/> if this element is root.
        /// </para>
        /// </param>
        /// <param name="element">
        /// <para>
        /// Type: <see cref="XmlElement"/>
        /// </para>
        /// <para>
        /// The element that represents this object
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="part"/> or <paramref name="element"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="SerializationException">
        /// This element has a same <see cref="ID"/> as another that was already deserialized.
        /// </exception>
        protected ModelElement(XmlPackagePart part, ModelElement owner, XmlElement element)
        {
            if (part == null)
                throw new ArgumentNullException("part");
            if (element == null)
                throw new ArgumentNullException("element");

            if (owner == null)
                this.part = part;
            this.owner = owner;
            this.element = element;
            this.validationRules = new Dictionary<string, Rule[]>();
            this.PropertyChanged += new PropertyChangedEventHandler(part.Content_PropertyChanged);

            idAttribute = element.Attributes[PackageVocabulary.IDAttribute];

            if (idAttribute != null)
            {
                try
                {
                    part.Package.IDMap.InsertObject(this, ID);
                }
                catch (ArgumentException e)
                {
                    throw new SerializationException("Duplicate element ID found in the package!", element.OuterXml, e);
                }
            }
        }

        #endregion

        #region INotifyPropertyChanged Members

        /// <summary>
        /// Occurs when the value of some property has changed.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        #region IValidator Members

        /// <inheritdoc/>
        public Rule[] GetValidationRules(string propertyName)
        {
            if (!validationRules.ContainsKey(propertyName))
                validationRules[propertyName] = CreateValidationRules(propertyName);

            return validationRules[propertyName];
        }

        /// <inheritdoc/>
        public bool ValidatePropertyValue(string propertyName, object value, out Rule[] brokenRules)
        {
            Rule[] rules = GetValidationRules(propertyName);
            List<Rule> broken = null;

            if (rules != null)
            {
                foreach (Rule rule in rules)
                {
                    if (!rule.ValidateValue(value))
                    {
                        if (broken == null)
                            broken = new List<Rule>();

                        broken.Add(rule);
                    }
                }
            }

            if (broken != null)
            {
                brokenRules = broken.ToArray();
                return false;
            }
            else
            {
                brokenRules = null;
                return true;
            }
        }

        /// <inheritdoc/>
        public bool ValidateAndSetPropertyValue(string propertyName, object value, out Rule[] brokenRules)
        {
            if (ValidatePropertyValue(propertyName, value, out brokenRules))
            {
                SetPropertyValueCore(propertyName, value);
                return true;
            }

            return false;
        }

        #endregion

        #region IDisposable Members

        /// <inheritdoc/>
        public virtual void Dispose()
        {
            if (PackagePart != null)
                PackagePart.Package.IDMap.RemoveObject(ID);

            if (ElementDisposed != null)
                ElementDisposed(this, EventArgs.Empty);
        }

        #endregion

        #region Events

        /// <summary>
        /// Occurs when this element has been explicitly disposed using the <see cref="Dispose"/> method.
        /// </summary>
        /// <remarks>
        /// This is a signal for other elements that reference this one that the element has gone out of scope
        /// and that they should delete their references.
        /// </remarks>
        public event EventHandler ElementDisposed;

        #endregion

        #region Protected Methods

        /// <summary>
        /// When overriden in a derived class, creates a new XML element
        /// that will represent this model element and sets its data fields
        /// to default values.
        /// </summary>
        /// <remarks>
        /// The <see cref="ModelElement"/> class adds an XML attribute called <see cref="PackageVocabulary.IDAttribute"/>
        /// to the element created by this method. This attribute will hold the value of the <see cref="ID"/> property.
        /// A derived class must not insert an attribute with the same name to the element.
        /// </remarks>
        /// <param name="document">
        /// <para>
        /// Type: <see cref="XmlDocument"/>
        /// </para>
        /// <para>
        /// The XML document that will own the element.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="XmlElement"/>
        /// </para>
        /// <para>
        /// The created element.
        /// </para>
        /// </returns>
        protected abstract XmlElement CreateObjectElement(XmlDocument document);

        /// <summary>
        /// Creates a set of validation rules that constraint the value of a given property.
        /// </summary>
        /// <remarks>
        /// <para>
        /// The default implementation returns <see langword="null"/> meaning <i>no rule imposed</i>
        /// for each property. The derived class should override this method if it needs to constraint
        /// the value of some property.
        /// </para>
        /// <para>
        /// <b>Caution:</b><br/>
        /// If the class derives from a class that overrides <see cref="CreateValidationRules"/>
        /// do not forget to call the base class's <see cref="CreateValidationRules"/>.
        /// Otherwise you would ignore the rules imposed on the base class's properties.
        /// </para>
        /// <para>
        /// This method is called only the first time the rules for the property are requested,
        /// next time they are retrieved from internal cache.
        /// </para>
        /// </remarks>
        /// <param name="propertyName">
        /// <para>
        /// Type: <see cref="System.String"/>
        /// </para>
        /// <para>
        /// Name of the property for which the validation rules are requested.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="System.Array"/> of <see cref="Rule"/>
        /// </para>
        /// <para>
        /// Collection of rules that constraint the value of the property or <see langword="null"/>
        /// if no rule is imposed.
        /// </para>
        /// </returns>
        protected virtual Rule[] CreateValidationRules(string propertyName)
        {
            return null;
        }

        /// <summary>
        /// Gets the value of a given property.
        /// </summary>
        /// <remarks>
        /// <para>
        /// This method is called from <see cref="GetPropertyValue"/> method.
        /// </para>
        /// </remarks>
        /// <param name="propertyName">
        /// <para>
        /// Type: <see cref="System.String"/>
        /// </para>
        /// <para>
        /// Name of the property
        /// </para>
        /// </param>
        /// <param name="value">
        /// <para>
        /// Type: <see cref="System.Object"/>
        /// </para>
        /// <para>
        /// The property value.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="System.Boolean"/>
        /// </para>
        /// <para>
        /// The value indicating whether the property was found on the class or not.
        /// </para>
        /// </returns>
        protected virtual bool GetPropertyValueCore(string propertyName, out object value)
        {
            // Get the structure of the requested property
            var propInfo = GetType().GetProperty(propertyName);
            if (propInfo == null)
            {
                value = null;
                return false;
            }

            // Get the getter for the requested property
            var propGetInfo = propInfo.GetGetMethod();
            if (propGetInfo == null)
            {
                value = null;
                return false;
            }

            // Set the value of the property
            value = propGetInfo.Invoke(this, null);
            return true;
        }

        /// <summary>
        /// Raises the <see cref="PropertyChanged"/> event on this <see cref="ModelElement"/> instance.
        /// </summary>
        /// <param name="propertyName"></param>
        protected void RaisePropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        /// <summary>
        /// Sets the value of the given property to a given value.
        /// </summary>
        /// <remarks>
        /// <para>
        /// This method is called from <see cref="ValidateAndSetPropertyValue"/>, therefore if some
        /// validation rules were imposed on the property value they were not broken.<br/>
        /// However the validation procedure cannot handle the following cases:
        /// <list type="bullet">
        /// <item>
        ///     The value type is not valid for the property, but no validation rule was imposed on the property
        ///     or it did not check the type.
        /// </item>
        /// <item>
        ///     The property does not exist on the class.
        /// </item>
        /// <item>
        ///     The property is not writeable.
        /// </item>
        /// </list>
        /// Therefore the method can still throw a <see cref="ValidationException"/>.
        /// </para>
        /// <para>
        /// The method uses reflection to perform the task.
        /// </para>
        /// </remarks>
        /// <param name="propertyName">
        /// <para>
        /// Type: <see cref="System.String"/>
        /// </para>
        /// <para>
        /// The name of the property that is being set
        /// </para>
        /// </param>
        /// <param name="value">
        /// <para>
        /// Type: <see cref="System.Object"/>
        /// </para>
        /// <para>
        /// The new value for the property.
        /// </para>
        /// </param>
        /// <exception cref="ValidationException">
        /// The <paramref name="value"/> has not the correct type.<br/>
        /// -or-<br/>
        /// No property named <paramref name="propertyName"/> exists on the class.<br/>
        /// -or-<br/>
        /// The property exists, but is not writeable.
        /// </exception>
        protected void SetPropertyValueCore(string propertyName, object value)
        {
            // Get the structure of the requested property
            var propInfo = GetType().GetProperty(propertyName);
            if (propInfo == null)
                throw new ValidationException(String.Format("The property {0} does not exist on {1}!", propertyName, GetType().Name));

            // Get the getter for the requested property
            var propSetInfo = propInfo.GetSetMethod();
            if (propSetInfo == null)
                throw new ValidationException(String.Format("The property {0}.{1} is not writeable!", GetType().Name, propertyName));

            // Set the value of the property
            propSetInfo.Invoke(this, new object[] { value });
        }

        /// <summary>
        /// Checks if a given value is valid for a given property and if the validation fails,
        /// throws <see cref="ValidationException"/> filled with the property name and broken validation rules.
        /// </summary>
        /// <param name="propertyName">
        /// <para>
        /// Type: <see cref="System.String"/>
        /// </para>
        /// <para>
        /// Name of the property
        /// </para>
        /// </param>
        /// <param name="value">
        /// <para>
        /// Type: <see cref="System.Object"/>
        /// </para>
        /// <para>
        /// The validated value
        /// </para>
        /// </param>
        /// <exception cref="ValidationException">
        /// The validation fails (the <paramref name="value"/> is not valid for the property).
        /// </exception>
        protected void ValidateAndThrow(string propertyName, object value)
        {
            Rule[] brokenRules;

            if (!ValidatePropertyValue(propertyName, value, out brokenRules))
                throw new ValidationException(propertyName, brokenRules);
        }

        #endregion

        #region Internal Properties

        /// <summary>
        /// Gets the XML element that represents this object.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="XmlElement"/>
        /// </para>
        /// <para>
        /// The XML element that represents this object.
        /// </para>
        /// </value>
        internal XmlElement Element
        {
            get { return element; }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Clones this model element to another owner.
        /// </summary>
        /// <remarks>
        /// <para>
        /// This method creates a deep copy of the element by cloning its underlaying XML element
        /// and loading it via the deserialization constructor (<see cref="ModelElement(XmlPackagePart, ModelElement, XmlElement)"/>).
        /// Hence, in order to use it, the element must implement this constfutor, otherwise an exception is thrown.
        /// </para>
        /// <para>
        /// This method just creates a copy of the element and sets its owner to a given element, but does not append it
        /// to the <paramref name="newOwner"/>'s tree, this is the responsibility of the caller (the <see cref="CloneTo"/> method
        /// cannot know where exactly paste it should paste the clone).
        /// </para>
        /// </remarks>
        /// <param name="newOwner">
        /// <para>
        /// Type: <see cref="ModelElement"/>
        /// </para>
        /// <para>
        /// The model element that will own the clone.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="ModelElement"/>
        /// </para>
        /// <para>
        /// The created deep copy of this element.
        /// </para>
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="newOwner"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="SerializationException">
        /// The element does not implement the deserialization constructor.<br/>
        /// -or-<br/>
        /// An error occured during the deserialization of the element.
        /// </exception>
        public ModelElement CloneTo(ModelElement newOwner)
        {
            if (newOwner == null)
                throw new ArgumentNullException("newOwner");

            XmlElement cloneElement;
            var ownerPart = newOwner.PackagePart;
            var myPart = PackagePart;

            if (ownerPart == myPart)
                cloneElement = (XmlElement)element.Clone();
            else
                cloneElement = (XmlElement)ownerPart.Document.ImportNode(element, true);

            try
            {
                var childType = GetType();
                var ctor = childType.GetConstructor(
                    BindingFlags.CreateInstance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance,
                    null,
                    new Type[] { ownerPart.GetType(), newOwner.GetType(), typeof(XmlElement) },
                    null
                    );
                
                if (ctor == null)
                    throw new SerializationException("This element does not implement the deserialization constructor!", element.OuterXml);

                var el = (ModelElement)ctor.Invoke(new object[] { ownerPart, newOwner, cloneElement });

                return el;
            }
            catch (SerializationException)
            {
                throw;
            }
            catch (Exception e)
            {
                throw new SerializationException("Could not deserialize the cloned element!", cloneElement.OuterXml, e);
            }
        }

        /// <summary>
        /// Finds the model element of a given type that owns (directly or indirectly) this element.
        /// </summary>
        /// <typeparam name="T">Type of the searched owner (must be a subtype of <see cref="ModelElement"/>)</typeparam>
        /// <returns>
        /// <para>
        /// Type: <typeparamref name="T"/>
        /// </para>
        /// <para>
        /// The element of type <typeparamref name="T"/> that owns (directly or indirectly) this element
        /// or <see langword="null"/> if this element is a root element or none of its owners has the given type.
        /// </para>
        /// </returns>
        public T FindOwner<T>() where T : ModelElement
        {
            var el = owner;

            while (el != null && !(el is T))
                el = el.owner;

            return (T)el;
        }

        /// <summary>
        /// Returns the value of the property with a given name.
        /// </summary>
        /// <remarks>
        /// This method internally calls <see cref="GetPropertyValueCore"/>. 
        /// The derived class is responsible for retrieving the value
        /// of its declared property. (The properties defined in base classes
        /// are taken care of by the base classes).
        /// </remarks>
        /// <param name="propertyName">
        /// <para>
        /// Type: <see cref="System.String"/>
        /// </para>
        /// <para>
        /// The name of the property
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="System.Object"/>
        /// </para>
        /// <para>
        /// The property value
        /// </para>
        /// </returns>
        /// <exception cref="ArgumentException">
        /// The property named <paramref name="propertyName"/> does not exist on the class.
        /// </exception>
        public object GetPropertyValue(string propertyName)
        {
            object value;

            if (!GetPropertyValueCore(propertyName, out value))
                throw new ArgumentException("The property " + propertyName + " does not exist on the class!", "propertyName");

            return value;
        }

        /// <summary>
        /// Loads a <see cref="ModelElement"/> from its serialized XML representation.
        /// </summary>
        /// <remarks>
        /// In order to use this method the XML element must have an attribute called <see cref="PackageVocabulary.TypeAttribute"/>
        /// whose value is a full type name of the serialized <see cref="ModelElement"/>.
        /// </remarks>
        /// <typeparam name="T">
        /// The type of the deserialized element. This can be any subclass of the <see cref="ModelElement"/>.
        /// </typeparam>
        /// <param name="packagePart">
        /// <para>
        /// Type: <see cref="XmlPackagePart"/>
        /// </para>
        /// <para>
        /// The package part that owns the element.
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="ModelElement"/>
        /// </para>
        /// <para>
        /// The model element that directly owns the deserialized element.
        /// </para>
        /// </param>
        /// <param name="element">
        /// <para>
        /// Type: <see cref="XmlElement"/>
        /// </para>
        /// <para>
        /// The XML element that holds the object data.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <typeparamref name="T"/>
        /// </para>
        /// <para>
        /// The deserialized model element.
        /// </para>
        /// </returns>
        /// <exception cref="ArgumentException">
        /// <paramref name="element"/> does not have the type attribute.
        /// </exception>
        /// <exception cref="SerializationException">
        /// An error occured during the deserialization.
        /// </exception>
        public static T LoadElement<T>(XmlPackagePart packagePart, ModelElement owner, XmlElement element) where T : ModelElement
        {
            if (packagePart == null)
                throw new ArgumentNullException("packagePart");
            if (element == null)
                throw new ArgumentNullException("element");

            var myAssembly = Assembly.GetExecutingAssembly();
            var typeAttribute = element.Attributes["type"];
            
            if (typeAttribute == null)
                throw new ArgumentException("The XML element does not have a type attribute!", "element");

            Type ownerType = (owner != null ? owner.GetType() : typeof(ModelElement));

            try
            {
                var childType = myAssembly.GetType(typeAttribute.Value);
                var ctor = childType.GetConstructor(
                    BindingFlags.CreateInstance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance,
                    null,
                    new Type[] { packagePart.GetType(), ownerType, typeof(XmlElement) },
                    null
                    );
                if (ctor == null && owner == null)
                {
                    ctor = childType.GetConstructor(
                        BindingFlags.CreateInstance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance,
                        null,
                        new Type[] { packagePart.GetType(), typeof(XmlElement) },
                        null
                        );
                }

                if (ctor == null)
                    throw new SerializationException("The element type does not have a compatible constructor!", element.OuterXml);

                var el = (T)ctor.Invoke(new object[] { packagePart, owner, element });

                return el;
            }
            catch (SerializationException)
            {
                throw;
            }
            catch (Exception e)
            {
                throw new SerializationException("Could not deserialize the element!", element.OuterXml, e);
            }
        }

        /// <summary>
        /// Migrates this element and its content to a new owner.
        /// </summary>
        /// <remarks>
        /// <para>
        /// This method sets the <see cref="Owner"/> property to <paramref name="newOwner"/>.
        /// If the <paramref name="newOwner"/> is in a different <see cref="PackagePart"/> it also imports the element's
        /// XML element to the new document, unbinds the old part's <see cref="PropertyChanged"/> event handler, binds 
        /// the one of the new <see cref="XmlPackagePart"/> and sets the new <see cref="ModelElement.PackagePart"/> property value.
        /// </para>
        /// <para>
        /// However, this method does not remove this element from the old owner, nor does it paste it to the new one.
        /// This is the responsibility of the caller.
        /// </para>
        /// </remarks>
        /// <param name="newOwner">
        /// <para>
        /// Type: <see cref="ModelElement"/>
        /// </para>
        /// <para>
        /// The new owner of this element.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="newOwner"/> is <see langword="null"/>.
        /// </exception>
        public void MigrateTo(ModelElement newOwner)
        {
            if (newOwner == null)
                throw new ArgumentNullException("newOwner");

            var ownerPart = newOwner.PackagePart;
            var myPart = PackagePart;

            if (ownerPart != myPart)
            {
                element = (XmlElement)ownerPart.Document.ImportNode(element, true);
                PropertyChanged -= myPart.Content_PropertyChanged;
                PropertyChanged += new PropertyChangedEventHandler(ownerPart.Content_PropertyChanged);
            }

            this.owner = newOwner;
            RaisePropertyChanged("Owner");
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets the unique identifier of this element.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Guid"/>
        /// </para>
        /// <para>
        /// The unique identifier of this model element.<br/>
        /// This value is persistent and thus can be used for serializing references
        /// between elements.
        /// The uniqueness is ensured in the scope of the owner <see cref="Package"/>.
        /// </para>
        /// </value>
        public Guid ID
        {
            get 
            {
                if (idAttribute == null)
                {
                    idAttribute = PackagePart.Document.CreateAttribute(PackageVocabulary.IDAttribute);
                    Element.Attributes.Append(idAttribute);

                    idAttribute.Value = Guid.NewGuid().ToString();
                }
                return new Guid(idAttribute.Value); 
            }
        }

        /// <summary>
        /// Gets the model element that directly owns this element.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="ModelElement"/>
        /// </para>
        /// <para>
        /// The model element that is the direct parent (owner) of this element.<br/>
        /// <see langword="null"/> if this element is a root element.
        /// </para>
        /// </value>
        public ModelElement Owner
        {
            get { return owner; }
        }

        /// <summary>
        /// Gets the package part that owns this model element.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="XmlPackagePart"/>
        /// </para>
        /// <para>
        /// The package part that directly or indirectly owns this element.
        /// </para>
        /// </value>
        public XmlPackagePart PackagePart
        {
            get 
            {
                if (owner == null)
                    return part;
                else
                {
                    var el = this;
                    while (el.Owner != null)
                        el = el.Owner;

                    return el.part;
                }
            }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the XML element that represents this object.
        /// </summary>
        private XmlElement element;
        /// <summary>
        /// Holds the XML attribute that holds the ID of this object.
        /// </summary>
        private XmlAttribute idAttribute;
        /// <summary>
        /// Holds a reference to the model element that directly owns this element.
        /// </summary>
        private ModelElement owner;
        /// <summary>
        /// Holds a reference to the package part that owns this element.
        /// </summary>
        private XmlPackagePart part;
        /// <summary>
        /// Holds a cache of validation rules.
        /// </summary>
        private Dictionary<string, Rule[]> validationRules;

        #endregion
    }
}
